package com.dj.common.web.plugin;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import javax.xml.bind.PropertyException;

import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.executor.ErrorContext;
import org.apache.ibatis.executor.ExecutorException;
import org.apache.ibatis.executor.statement.BaseStatementHandler;
import org.apache.ibatis.executor.statement.RoutingStatementHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.ParameterMode;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.property.PropertyTokenizer;
import org.apache.ibatis.scripting.xmltags.ForEachSqlNode;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.type.TypeHandler;
import org.apache.ibatis.type.TypeHandlerRegistry;

import com.dj.common.web.vo.BaseVo;
import com.dj.common.web.vo.PageAndSortVo;
import com.dj.core.ReflectHelper;
import com.dj.core.StringUtil;
import com.dj.core.Tools;


@Intercepts({ @Signature(type = StatementHandler.class, method = "prepare", args = { Connection.class,Integer.class }) })
public class PagePlugin implements Interceptor {

	private static String dialect = ""; 
	private static String pageSqlId = "";

	public Object intercept(Invocation ivk) throws Throwable {
		if (ivk.getTarget() instanceof RoutingStatementHandler) {
			RoutingStatementHandler statementHandler = (RoutingStatementHandler) ivk
					.getTarget();
			BaseStatementHandler delegate = (BaseStatementHandler) ReflectHelper
					.getValueByFieldName(statementHandler, "delegate");
			MappedStatement mappedStatement = (MappedStatement) ReflectHelper
					.getValueByFieldName(delegate, "mappedStatement");
			if (StringUtils.indexOf(mappedStatement.getId(), pageSqlId)>=0) { 
				BoundSql boundSql = delegate.getBoundSql();
				Object parameterObject = boundSql.getParameterObject();
				if (parameterObject!= null) {
					
					if (parameterObject instanceof PageAndSortVo) {
						PageAndSortVo<BaseVo> page = (PageAndSortVo) parameterObject;
						String sql = boundSql.getSql();
						if(page.isCount()){
							Connection connection = (Connection) ivk.getArgs()[0];
							String countSql = "select count(0) ";
							if(null!=page.getVo()){
								/*for(Object entry: page.getVo().entrySet()){
									Map.Entry<String,String> a = (Map.Entry<String,String>)entry;
									countSql+=","+a.getValue() +" as "+a.getKey();
								}*/
							}
							countSql +=" from (" + sql+ ")  tmp_count";
							
							PreparedStatement countStmt = connection
									.prepareStatement(countSql);
							BoundSql countBS = new BoundSql(
									mappedStatement.getConfiguration(), countSql,
									boundSql.getParameterMappings(),
									parameterObject);
							setParameters(countStmt, mappedStatement, countBS,
									parameterObject);
							ResultSet rs = countStmt.executeQuery();
							int count = 0;
							if (rs.next()) {
								count = rs.getInt(1);
								/*if(null!=page.getPd()){
									for(Object entry: page.getPd().entrySet()){
										Map.Entry<String,String> a = (Map.Entry<String,String>)entry;
										page.getPd().put(a.getKey(), rs.getObject(a.getKey()));
									}
								}*/
							}							
							rs.close();
							countStmt.close();
							page.setTotalResult(count);
						}
						String pageSql = generatePageSql(sql, page);
						ReflectHelper.setValueByFieldName(boundSql, "sql",
								pageSql);
					}

				}
			}
		}
		return ivk.proceed();
	}

	/**
	 * 瀵筍QL鍙傛暟(?)璁惧��,鍙傝�僶rg.apache.ibatis.executor.parameter.
	 * DefaultParameterHandler
	 * 
	 * @param ps
	 * @param mappedStatement
	 * @param boundSql
	 * @param parameterObject
	 * @throws SQLException
	 */
	private void setParameters(PreparedStatement ps,
			MappedStatement mappedStatement, BoundSql boundSql,
			Object parameterObject) throws SQLException {
		ErrorContext.instance().activity("setting parameters")
				.object(mappedStatement.getParameterMap().getId());
		List<ParameterMapping> parameterMappings = boundSql
				.getParameterMappings();
		if (parameterMappings != null) {
			Configuration configuration = mappedStatement.getConfiguration();
			TypeHandlerRegistry typeHandlerRegistry = configuration
					.getTypeHandlerRegistry();
			MetaObject metaObject = parameterObject == null ? null
					: configuration.newMetaObject(parameterObject);
			for (int i = 0; i < parameterMappings.size(); i++) {
				ParameterMapping parameterMapping = parameterMappings.get(i);
				if (parameterMapping.getMode() != ParameterMode.OUT) {
					Object value;
					String propertyName = parameterMapping.getProperty();
					PropertyTokenizer prop = new PropertyTokenizer(propertyName);
					if (parameterObject == null) {
						value = null;
					} else if (typeHandlerRegistry
							.hasTypeHandler(parameterObject.getClass())) {
						value = parameterObject;
					} else if (boundSql.hasAdditionalParameter(propertyName)) {
						value = boundSql.getAdditionalParameter(propertyName);
					} else if (propertyName
							.startsWith(ForEachSqlNode.ITEM_PREFIX)
							&& boundSql.hasAdditionalParameter(prop.getName())) {
						value = boundSql.getAdditionalParameter(prop.getName());
						if (value != null) {
							value = configuration.newMetaObject(value)
									.getValue(
											propertyName.substring(prop
													.getName().length()));
						}
					} else {
						value = metaObject == null ? null : metaObject
								.getValue(propertyName);
					}
					TypeHandler typeHandler = parameterMapping.getTypeHandler();
					if (typeHandler == null) {
						throw new ExecutorException(
								"There was no TypeHandler found for parameter "
										+ propertyName + " of statement "
										+ mappedStatement.getId());
					}
					typeHandler.setParameter(ps, i + 1, value,
							parameterMapping.getJdbcType());
				}
			}
		}
	}

	/**
	 * 鏍规嵁鏁版嵁搴撴柟瑷�锛岀敓鎴愮壒瀹氱殑鍒嗛〉sql
	 * 
	 * @param sql
	 * @param page
	 * @return
	 */
	private String generatePageSql(String sql, PageAndSortVo page) {
		if (page != null && Tools.notEmpty(dialect)) {
			StringBuffer pageSql = new StringBuffer();
			if ("mysql".equals(dialect)) {
				pageSql.append(sql);
				if(!StringUtils.isEmpty(page.getSort())){
					pageSql.append(" order by "+page.getSort());
				}
				if(page.getLimit()>0){
					pageSql.append(" limit " + page.getOffset() + ","
							+ page.getLimit());
				}
			} else if ("oracle".equals(dialect)) {
				pageSql.append("select * from (select tmp_tb.*,ROWNUM row_id from (");
				pageSql.append(sql);
				pageSql.append(") tmp_tb where ROWNUM<=");
				pageSql.append(page.getOffset() + page.getLimit());
				pageSql.append(") where row_id>");
				pageSql.append(page.getOffset());
			}else if("mssql".equals(dialect)){
				if(page.getOffset()>=0&&page.getLimit()>0){
					if(!StringUtils.isEmpty(page.getSort())){
						String[] os = page.getSort().split(" ");
						if(os.length==2){
							String os0 = os[0];
							if(os0.indexOf("_")>0){
								sql = sql+" order by "+os[0]+" "+os[1];
							}else{
								sql = sql+" order by "+StringUtil.upperCharToUnderLine(os[0])+" "+os[1];
							}
						}else{
							sql = sql+" order by "+page.getSort();
							
						}
					}
					pageSql.append((new SqlServer()).convertToPageSql(sql, page.getOffset(),page.getLimit()));
				}else{
					pageSql.append(sql);
				}
			}
			return pageSql.toString();
		} else {
			return sql;
		}
	}

	public Object plugin(Object arg0) {
		// TODO Auto-generated method stub
		return Plugin.wrap(arg0, this);
	}

	public void setProperties(Properties p) {
		dialect = p.getProperty("dialect");
		if (Tools.isEmpty(dialect)) {
			try {
				throw new PropertyException("dialect property is not found!");
			} catch (PropertyException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		pageSqlId = p.getProperty("pageSqlId");
		if (Tools.isEmpty(pageSqlId)) {
			try {
				throw new PropertyException("pageSqlId property is not found!");
			} catch (PropertyException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}

}
